//
// Created by hjie on 23-10-6.
//
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <iostream>
#include <memory>
#include "define.h"
#include "loop_event/loop_event_define.h"
#include "message_attr.h"
#include "stun_message.h"
#include "turn_client_main/client_process.h"
#include "turn_client_main/file_operation.h"
#include "turn_client_main/peer_client_process.h"
#include "turn_client_main/util.h"
#include "udp_client.h"

static LoopEvent loop;
static UdpClient * client;
static UdpClient * peer_client;
static AllocateDataInformation information{};
static PeerClientInformation   peer_information{};

void StartTurnClient()
{

    StunMessage message;
    message.SetMsgType(EmBindRequest);
    message.m_transaction_id = message.ProducerTransactionId();
    message.m_magic_cookie   = magic_cookie;

    WriteBuffer buffer;
    message.ComposeStunPacket(buffer);

    client->DoSendMsg(buffer.Data(), buffer.Length());

}

void StartPeerTurnClient()
{
    StunMessage message;
    message.SetMsgType(EmBindRequest);
    message.m_transaction_id = message.ProducerTransactionId();
    message.m_magic_cookie   = magic_cookie;

    WriteBuffer buffer;
    message.ComposeStunPacket(buffer);
    peer_client->DoSendMsg(buffer.Data(), buffer.Length());
}

void ProcessMessage(StunMessage & message, UdpBufferPacket * packet)
{

    switch (message.GetMsgType()) 
    {
        case EmBindResponse:
        {
            printf("receive bind request response message.\n");
            ProcessBindResponse(&loop, client, message);
            break;
        }
        case EmAllocateErrorResponse:
        {
            printf("receive allocate error response.\n");
            ProcessAllocateErrorResponse(&loop, client, message, information);
            break;
        }
        case EmAllocateSuccessResponse:
        {
            printf("receive allocate success response.\n");
            ProcessAllocateSuccessResponse(&loop, client, message, information, peer_information);
            break;
        }
        case EmRefreshRequestResponse:
        {
            printf("receive EmRefreshRequestResponse.\n");
            break;
        }
        case EmCreatePermissionResponse:
        {
            printf("receive EmCreatePermissionResponse.\n");
            AddPermissionSuccessResponse(&loop, client, message, information, peer_information);
            break;
        }
        case EmDataIndication:
        {
            // ProcessDataIndication(&loop, client, message, information, peer_information);
            break;
        }
        default:
            break;
    }
    //printf("process message type : %d.\n", message.GetMsgType());
}


void ProcessPeerClientMessage(StunMessage & message, UdpBufferPacket * packet)
{
    switch (message.GetMsgType()) 
    {
        case EmBindResponse:
        {
            printf("receive bind request response message.\n");
            PeerProcessBindResponse(&loop, client, message, peer_information);
            break;
        }
        case EmAllocateErrorResponse:
        {
            printf("receive allocate error response.\n");
            break;
        }
        case EmAllocateSuccessResponse:
        {
            printf("receive allocate success response.\n");
            break;
        }
        case EmRefreshRequestResponse:
        {
            printf("receive EmRefreshRequestResponse.\n");
            break;
        }
        case EmCreatePermissionResponse:
        {
            printf("receive EmCreatePermissionResponse.\n");
            break;
        }
        default:
            break;
    }
}

void ParseMessage(UdpBufferPacket * packet, int mode = 0)
{
    do
    {
        int use_length = 0;
        StunMessage message;
        ParseStatus status = message.ParseStunMessage(packet, use_length);

        printf("parse result : %d.\n", status);

        if (status == EmParseFinishStatus)
        {
            packet->buffer->RemoveData(use_length);
            if (mode == 0)
            {
                ProcessMessage(message, packet);
            }
            else 
            {
                ProcessPeerClientMessage(message, packet);
            }
            break;
        }
        else if (status == EmUnknownStatus)
        {
            // drop packet
            packet->buffer->RemoveData(packet->buffer->Length());
            break;
        }
        else 
        {

            packet->buffer->RemoveData(packet->buffer->Length());
            break;
        }

    }while (true);
}

void OnUdpMessageFunc(UdpBufferPacket * packet)
{
    char buffer[128] = {0};
    uint16_t port = 0;

    auto * sai = (struct sockaddr_in*)packet->sai;
    port = ntohs(sai->sin_port);
    inet_ntop(AF_INET, &sai->sin_addr.s_addr, buffer, sizeof(buffer));

    printf("from %s:%d data length : %d.\n", buffer, port, packet->buffer->Length());

    ParseMessage(packet);
    // remove all data
    packet->buffer->RemoveData(packet->buffer->Length());
}

void OnPeerClientUdpMessageFunc(UdpBufferPacket * packet)
{
    char buffer[128] = {0};
    uint16_t port = 0;

    auto * sai = (struct sockaddr_in*)packet->sai;
    port = ntohs(sai->sin_port);
    inet_ntop(AF_INET, &sai->sin_addr.s_addr, buffer, sizeof(buffer));

    printf("from %s:%d data length : %d.\n", buffer, port, packet->buffer->Length());

    uint16_t server_port = ntohs(sai->sin_port);

    uint8_t * data = packet->buffer->Data();
    if (/*compare server port*/server_port == 10024 )
    {
        // stun message 
        ParseMessage(packet, 1);
    }
    else 
    {
        printf("receive data from server port : %d.\n", server_port);
        ProcessDataFromRelayServer(&loop, peer_client, peer_information, packet);
    }
    // remove all data
    packet->buffer->RemoveData(packet->buffer->Length());
}

int main(int argc, const char * argv[])
{
    if (argc < 2)
    {
        printf("Usage: turn_client_main server_ip.\n");
        return EXIT_FAILURE;
    }
    const std::string & server_ip = argv[1];
    const uint16_t port = 10024;
    client = new UdpClient(&loop, server_ip, port);


    // test user name code
    // {

    //     for (int i = 0; i < 20; i++)
    //     {
    //         std::map<std::string, int> user_name_map;
    //         for (int index = 0; index < 1000; index++)
    //         {
    //             std::string result = RandomUserName(6);
    //             // printf("user name : %s.\n", result.c_str());
    //             user_name_map[result]++;
    //         }
    //         printf("user name map size : %ld.\n", user_name_map.size());
    //     }
    //     exit(-1);
    // }

    // send data
    information.m_file_operation.m_file_name = "/home/hjie/source/open_net/ffmpeg/resource/sample4.opus";

    if (!InitFileOperation(information.m_file_operation, 1))
    {
        printf("init fail file %s and mode is : 1.\n", information.m_file_operation.m_file_name.c_str());
        delete client;
        client = nullptr;
        return 0;
    }
    peer_information.m_file_operation.m_file_name = "./save";

    auto index = information.m_file_operation.m_file_name.find(".");

    if (index != std::string::npos)
    {
        std::string format = information.m_file_operation.m_file_name.substr(index);
        peer_information.m_file_operation.m_file_name += format;
    }

    if (!InitFileOperation(peer_information.m_file_operation, 2))
    {

        printf("init fail file %s and mode is : 2.\n", peer_information.m_file_operation.m_file_name.c_str());
        delete client;
        client = nullptr;
        return 0;
    }

    peer_client = new UdpClient(&loop, server_ip, port);

    client->SetOnMessage(OnUdpMessageFunc);

    client->StartClient();

    loop.PushTask([&](){

        StartTurnClient();
    });

    peer_client->SetOnMessage(OnPeerClientUdpMessageFunc);

    peer_client->StartClient();


    loop.PushTask([&](){

        peer_information.m_relay_client = peer_client;
        StartPeerTurnClient();
    });
    loop.Run();
    return EXIT_SUCCESS;
}